package com.huangfu.learning.javabase.java.pattern.action.observer;

import java.util.ArrayList;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * Author: huangfu.daohui
 * Date: 2018/8/8
 * Time: 15:36
 * Version: 1.0
 * Description:观察者模式：当对象间存在一对多关系时。比如，当一个对象被修改时，则会自动通知它的依赖对象。
 * 参考：https://www.cnblogs.com/V1haoge/p/6513651.html
 * https://www.cnblogs.com/luohanguo/p/7825656.html
 * <p>
 * 意图：定义对象间的一种一对多的依赖关系，当一个对象的状态发生改变时，所有依赖于它的对象都得到通知并被自动更新。
 * 主要解决：一个对象状态改变给其他对象通知的问题，而且要考虑到易用和低耦合，保证高度的协作。
 * 何时使用：一个对象（目标对象）的状态发生改变，所有的依赖对象（观察者对象）都将得到通知，进行广播通知。
 * 如何解决：使用面向对象技术，可以将这种依赖关系弱化。
 * 关键代码：在抽象类里有一个 ArrayList 存放观察者们。
 * <p>
 * 四个角色
 * 抽象被观察者角色：也就是一个抽象主题，它把所有对观察者对象的引用保存在一个集合中，每个主题都可以有任意数量的观察者。抽象主题提供一个接口，可以增加和删除观察者角色。一般用一个抽象类和接口来实现。
 * 抽象观察者角色：为所有的具体观察者定义一个接口，在得到主题通知时更新自己。
 * 具体被观察者角色：也就是一个具体的主题，在集体主题的内部状态改变时，所有登记过的观察者发出通知。
 * 具体观察者角色：实现抽象观察者角色所需要的更新接口，一边使本身的状态与制图的状态相协调。
 * <p>
 * 优点： 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
 * 缺点： 1、如果一个被观察者对象有很多的直接和间接的观察者的话，将所有的观察者都通知到会花费很多时间。
 * 2、如果在观察者和观察目标之间有循环依赖的话，观察目标会触发它们之间进行循环调用，可能导致系统崩溃。
 * 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的，而仅仅只是知道观察目标发生了变化。
 **/
public class ObserverPattern {

    // 测试,模拟警察（观察者）抓小偷（目标、被观察者）
    public static void main(String[] args) {

        // 定义两个嫌疑犯
        Suspect beer = new Bear(); //嫌疑犯熊二
        Suspect dog = new Dog(); //嫌疑犯狗剩
        // 定义三个警察
        Police zhang = new BianyiPoliceZhang(); //便衣小张
        Police wang = new BianyiPoliceWang(); //便衣小王
        Police li = new BianyiPoliceLi(); //便衣小李

        //为嫌疑犯添加观察
        beer.addObserver(zhang);
        beer.addObserver(wang);
        beer.addObserver(li);

        dog.addObserver(zhang);
        dog.addObserver(wang);
        dog.addObserver(li);
        //定义嫌烦的动作
        String message1 = "又卖了一批货";
        String message2 = "老大要下来视察了";
        beer.notice(message1);
        dog.notice(message2);
    }
}

// 抽象观察者角色  警察
interface Police {
    void update(String message, String name);
}

//观察者1  便衣警察小张
class BianyiPoliceZhang implements Police {

    private String bname = "张三丰";

    @Override
    public void update(String message, String name) {
        System.out.println(bname + "说:" + name + "那里有新情况：" + message);
    }
}

//观察者2  便衣警察小王
class BianyiPoliceWang implements Police {

    private String bname = "王安石";

    @Override
    public void update(String message, String name) {
        System.out.println(bname + "说:" + name + "那里有新情况：" + message);
    }
}

//观察者3  便衣警察小李
class BianyiPoliceLi implements Police {

    private String bname = "李逵";

    @Override
    public void update(String message, String name) {
        System.out.println(bname + "说:" + name + "那里有新情况：" + message);
    }
}

// 抽象被观察者  嫌疑犯。声明了添加、删除、通知观察者方法
interface Suspect {
    //添加便衣观察者
    void addObserver(Police police);

    //移除便衣观察者
    void removeObserver(Police police);

    //通知观察者
    void notice(String message);
}

//具体的被观察者  嫌疑犯熊二
class Bear implements Suspect {

    // 嫌疑犯熊二
    private String name = "熊二";

    //定义观察者集合
    private List<Police> polices = new ArrayList<Police>();

    //添加观察者
    @Override
    public void addObserver(Police police) {
        if (!polices.contains(police)) {
            polices.add(police);
        }
    }

    //移除观察者
    @Override
    public void removeObserver(Police police) {
        if (polices.contains(police)) {
            polices.remove(police);
        }
    }

    //通知观察者
    @Override
    public void notice(String message) {
        for (Police police : polices) {
            police.update(message, name);
        }
    }
}

//具体的被观察者  嫌疑犯狗剩
class Dog implements Suspect {

    // 嫌疑犯熊二
    private String name = "狗剩";

    //定义观察者集合
    private List<Police> polices = new ArrayList<Police>();

    //添加观察者
    @Override
    public void addObserver(Police police) {
        if (!polices.contains(police)) {
            polices.add(police);
        }
    }

    //移除观察者
    @Override
    public void removeObserver(Police police) {
        if (polices.contains(police)) {
            polices.remove(police);
        }
    }

    //通知观察者
    @Override
    public void notice(String message) {
        for (Police police : polices) {
            police.update(message, name);
        }
    }
}